/*
 * GL_Actor.h
 *
 * Created 9/22/2008 By Johnny Huynh
 *
 * Version 00.00.04 7/6/2009
 *
 * Copyright Information:
 * All content copyright © 2008 Johnny Huynh. All rights reserved.
 */

 #ifndef GL_ACTOR_H
 #define GL_ACTOR_H 
 
 template <typename TYPENAME> class GL_Actor;
 
 #include "OpenGL_Headers.h"
 #include "Math3D.h"
 #include "Vector3.h"
 #include "OrientationMatrix.h"
 
 typedef GL_Actor<GLfloat> GL_Actorf;
 typedef GL_Actor<GLdouble> GL_Actord;
 
 #ifndef GL_PI
 #define GL_PI 3.1415f
 #endif // GL_PI
 
 /**
  * Class specification for GL_Actor
  */
 template <typename TYPENAME>
 class GL_Actor
 {
 // Data Members
 protected:    
    Vector3<TYPENAME> position; // (x, y, z)

    OrientationMatrix3<TYPENAME> orientation; // unit vectors for the local x, y, and z axes of this object
                                              // The object is assumed to be facing in the direction of its local z-axis.
                                              // The object's left side is in the direction of its local x-axis.
                                              // The object's up vector is in the direction of its local y-axis.

 // Local Functions
 public:
    GL_Actor( const TYPENAME posX = 0.0f, const TYPENAME posY = 0.0f, const TYPENAME posZ = 0.0f,
              const TYPENAME Xx = 1.0f, const TYPENAME Yx = 0.0f, const TYPENAME Zx = 0.0f,
              const TYPENAME Xy = 0.0f, const TYPENAME Yy = 1.0f, const TYPENAME Zy = 0.0f,
              const TYPENAME Xz = 0.0f, const TYPENAME Yz = 0.0f, const TYPENAME Zz = 1.0f );
    GL_Actor( const GL_Actor<TYPENAME>& a );
    ~GL_Actor( void );
    inline void operator=( const GL_Actor<TYPENAME>& a );
    inline void moveForward( const TYPENAME unit );
    inline void moveLeft( const TYPENAME unit );
    inline void translate( const TYPENAME x, const TYPENAME y, const TYPENAME z );
    inline void translate( const Vector3<TYPENAME>& displacement );
    inline void setPosition( const TYPENAME x, const TYPENAME y, const TYPENAME z );
    inline void setPosition( const Vector3<TYPENAME>& position );
    inline void setOrientation( const TYPENAME Xx, const TYPENAME Yx, const TYPENAME Zx,
                                const TYPENAME Xy, const TYPENAME Yy, const TYPENAME Zy,
                                const TYPENAME Xz, const TYPENAME Yz, const TYPENAME Zz );
    inline void setOrientation( const OrientationMatrix3<TYPENAME>& orientation );
    virtual inline void globalRotateLeft( const TYPENAME radian );
    virtual inline void globalRotateUp( const TYPENAME radian );
    virtual inline void globalTiltRight( const TYPENAME radian );
    inline void localRotateLeft( const TYPENAME radian );
    inline void localRotateUp( const TYPENAME radian );
    inline void localTiltRight( const TYPENAME radian );
    inline void transformCamera( void ) const;

    inline const OrientationMatrix3<TYPENAME>& getOrientation() const { return orientation; }
	inline const Vector3<TYPENAME>& getPosition() const { return position; }
	inline const Vector3<TYPENAME> getFaceAxis() const { return orientation.getZAxis(); }
	inline const Vector3<TYPENAME> getLeftAxis() const { return orientation.getXAxis(); }
	inline const Vector3<TYPENAME> getUpAxis() const { return orientation.getYAxis(); }
 
 // Private Functions
 private:
    
 // Friend Functions
 public:
    template <typename TYPENAME> friend inline void rotateMasterSlave( GL_Actor<TYPENAME>& master, GL_Actor<TYPENAME>& slave, 
                                               void(GL_Actor<TYPENAME>::*rotate)( const TYPENAME ), const TYPENAME radian );
 };
 
 /** LOCAL FUNCTIONS **/
 
 /**
  * GL_Actor() is the constructor for a GL_Actor object
  */
 template <typename TYPENAME>
 GL_Actor<TYPENAME>::GL_Actor( const TYPENAME posX, const TYPENAME posY, const TYPENAME posZ,
                     const TYPENAME Xx, const TYPENAME Yx, const TYPENAME Zx,
                     const TYPENAME Xy, const TYPENAME Yy, const TYPENAME Zy,
                     const TYPENAME Xz, const TYPENAME Yz, const TYPENAME Zz )
                    : position( posX, posY, posZ ), // Default location at origin
                      orientation( Xx, Yx, Zx,
                                   Xy, Yy, Zy,
                                   Xz, Yz, Zz )
 {

 }
 
 /**
  * Alternative Constructor
  */
 template <typename TYPENAME>
 GL_Actor<TYPENAME>::GL_Actor( const GL_Actor<TYPENAME>& a )
                    : orientation( a.orientation ), position( a.position )
 {
    //memcpy( this, &a, sizeof( GL_Actor<TYPENAME> ) );
 }

 /**
  * Destructor
  */
 template <typename TYPENAME>
 GL_Actor<TYPENAME>::~GL_Actor( void ){}
 
 /**
  * operator=() copies the content of the specified GL_Actor
  * to this GL_Actor.
  *
  * @param (const GL_Actor<TYPENAME>&) a
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::operator=( const GL_Actor<TYPENAME>& a )
 {
    memcpy( this, &a, sizeof( GL_Actor<TYPENAME> ) );
 }
 
 /**
  * moveForward() moves the actor forward by an amount specified by unit
  *
  * @param (const TYPENAME)unit
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::moveForward( const TYPENAME unit )
 {
    // The implementation works by interpreting the magnitude of vFaceX and vFaceZ as 1.
    // r^2 = x^2 + z^2    
    const TYPENAME r( calculateMagnitude( orientation.Zx, orientation.Zz ) );

    // 1 = (x^2/r^2) + (z^2/r^2)
    // x-unit = sqrt(x^2/r^2) = x / r
    // z-unit = sqrt(z^2/r^2) = z / r
    position.x += unit * ( orientation.Zx / r );
    position.z += unit * ( orientation.Zz / r );
 }
 
 /**
  * moveLeft() moves the actor left by an amount specified by unit
  *
  * @param (const TYPENAME)unit
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::moveLeft( const TYPENAME unit )
 {
    // The implementation works by interpreting the magnitude of vLeftX and vLeftZ as 1.
    // r^2 = x^2 + z^2    
    const TYPENAME r( calculateMagnitude( orientation.Xx, orientation.Xz ) );

    // 1 = (x^2/r^2) + (z^2/r^2)
    // x-unit = sqrt(x^2/r^2) = x / r
    // z-unit = sqrt(z^2/r^2) = z / r
    position.x += unit * ( orientation.Xx / r );
    position.z += unit * ( orientation.Xz / r );
 }
 
 /**
  * translate() moves the actor by the specified displacement vector.
  *
  * @param (const TYPENAME) x
  * @param (const TYPENAME) y
  * @param (const TYPENAME) z
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::translate( const TYPENAME x, const TYPENAME y, const TYPENAME z ) 
 { 
    position.x += x;
    position.y += y;
    position.z += z;
 }
 
 /**
  * translate() moves the actor by the specified displacement vector.
  *
  * @param (const Vector3<TYPENAME>&) displacement
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::translate( const Vector3<TYPENAME>& displacement )
 { 
    position += displacement;
 }
 
 /**
  * setPosition() sets the actor at the specified x, y, z coordinates position
  *
  * @param (const TYPENAME)x
  * @param (const TYPENAME)y
  * @param (const TYPENAME)z
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::setPosition( const TYPENAME x, const TYPENAME y, const TYPENAME z )
 {
    position.x = x;
    position.y = y;
    position.z = z;
 }
 
 /**
  * setPosition() sets the actor at the specified x, y, z coordinates position
  *
  * @param (const Vector3<TYPENAME>&) position
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::setPosition( const Vector3<TYPENAME>& position )
 {
    this->position = position;
 }
 
 /**
  * setOrientation() sets the actor to have the specified orientation,
  * where Xx, Xy, Xz denote the axis of the local x-axis, Yx, Yy, Yz 
  * denote the axis of the local y-axis, and Zx, Zy, Zz denote the
  * axis of the local z-axis.
  *
  * @param (const TYPENAME) Xx, 
  * @param (const TYPENAME) Xy, 
  * @param (const TYPENAME) Xz,
  * @param (const TYPENAME) Yx, 
  * @param (const TYPENAME) Yy, 
  * @param (const TYPENAME) Yz,
  * @param (const TYPENAME) Zx, 
  * @param (const TYPENAME) Zy, 
  * @param (const TYPENAME) Zz
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::setOrientation( const TYPENAME Xx, const TYPENAME Yx, const TYPENAME Zx,
                                                 const TYPENAME Xy,  const TYPENAME Yy, const TYPENAME Zy,
                                                 const TYPENAME Xz,  const TYPENAME Yz, const TYPENAME Zz )
 {
    orientation.Xx = Xx;
    orientation.Xy = Xy;
    orientation.Xz = Xz;
    orientation.Yx = Yx;
    orientation.Yy = Yy;
    orientation.Yz = Yz;
    orientation.Zx = Zx;
    orientation.Zy = Zy;
    orientation.Zz = Zz;
 }
 
 /**
  * setOrientation() sets the actor to have the specified orientation.
  *
  * @param (const OrientationMatrix3<TYPENAME>&) orientation
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::setOrientation( const OrientationMatrix3<TYPENAME>& orientation )
 {
    this->orientation = orientation;
 }
 
 /**
  * globalRotateLeft() rotates the actor left by the specified angle in radian 
  * (which affects the x-axis and z-axis).
  * The Actor is assumed to be facing in the direction of the positive z-axis
  * (local rotation depends on this assumption).
  *
  *     -z ^   
  *        |
  *  -x <-- --> x
  *        |
  *      z v
  *
  * @param (const TYPENAME)radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::globalRotateLeft( const TYPENAME radian )
 {
    orientation.globalRotateZX( radian );
 }

 /**
  * globalRotateUp() rotates the actor up by the specified angle in radian 
  * (which affects the z-axis and y-axis).
  * The Actor is assumed to be facing in the direction of the positive z-axis
  * (local rotation depends on this assumption).
  *
  *      y ^   
  *        |
  *  -z <-- --> z
  *        |
  *     -y v
  *
  * @param (const TYPENAME)radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::globalRotateUp( const TYPENAME radian )
 {
    orientation.globalRotateZY( radian );
 }

 /**
  * globalTiltRight() rotates the actor sideway by the specified angle in radian 
  * (which affects the x-axis and y-axis).
  * The Actor is assumed to be facing in the direction of the positive z-axis
  * (local rotation depends on this assumption).
  *
  *      y ^   
  *        |
  *  -x <-- --> x
  *        |
  *     -y v
  *
  * @param (const TYPENAME)radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::globalTiltRight( const TYPENAME radian )
 {
    orientation.globalRotateXY( radian );
 }

 /**
  * localRotateLeft() rotates the actor left by the specified angle in radian
  * relative to the camera(viewer).
  *
  * @param (const TYPENAME)radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::localRotateLeft( const TYPENAME radian )
 {
    orientation.localRotateZX( radian );
 }

 /**
  * localRotateUp() rotates the actor up by the specified angle in radian
  * relative to the camera(viewer).
  *
  * @param (const TYPENAME)radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::localRotateUp( const TYPENAME radian )
 {
    orientation.localRotateZY( radian );
 }
 
 /**
  * globalTiltRight() rotates the actor sideway by the specified angle in radian
  * relative to this actor's orientation.
  *
  * @param (const TYPENAME) radian
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::localTiltRight( const TYPENAME radian )
 {
    orientation.localRotateXY( radian );
 }
 
 /**
  * transformCamera() alters the OpenGL matrix of the user's camera/perspective
  */
 template <typename TYPENAME>
 inline void GL_Actor<TYPENAME>::transformCamera( void ) const
 {
    gluLookAt( position.x, position.y, position.z,
               position.x + orientation.Zx, position.y + orientation.Zy, position.z + orientation.Zz,
               orientation.Yx, orientation.Yy, orientation.Yz );

    /*gluLookAt( 0, 0, 0, 
           vFace.x, vFace.y, vFace.z,
           vTop[0], vTop[1], vTop[2] );
            
    glTranslatef( -position.x, -position.y, -position.z );*/
    
    // More efficient ActorGluLookAt()
    // // Would be more efficient if the x-vector was kept track of throughout rotations
    // x = vFace x vUp  // not vUp x vFace as that would be in the -x direction
    // float viewMatrix[] = { x[0], vUp.x, -vFace.x, 0.0f,
    //                       x[1], vUp.y, -vFace.y, 0.0f,
    //                       x[2], vUp.z, -vFace.z, 0.0f,
    //                       0.0f, 0.0f, 0.0f, 1.0f };
    // glMultMatrixf( viewMatrix );
    // glTranslatef( -position.x, -position.y, -position.z );
    
    // Works, but alignment is somewhat off for projectile object's location from cursor
    /*float viewMatrix[] = { -vLeft.x, vUp.x, -vFace.x, 0.0f,
                           -vLeft.y, vUp.y, -vFace.y, 0.0f,
                           -vLeft.z, vUp.z, -vFace.z, 0.0f,
                           0.0f, 0.0f, 0.0f, 1.0f };
    glMultMatrixf( viewMatrix );
    glTranslatef( -position.x, -position.y, -position.z );*/
 }
 
 /** FRIEND FUNCTIONS **/
 
 /**
  * rotateMasterSlave() applies the specified rotation (rotate function) to the master by the specified radian.
  * The slave is then rotated with respect to the master's rotation.
  * Example call to rotateMasterSlave():
  * rotateMasterSlave<GLfloat>( master_actor, slave_actor, &GL_Actor<GLfloat>::localRotateUp, 0.07f );
  *
  * @param (GL_Actor<TYPENAME>&) master
  * @param (GL_Actor<TYPENAME>&) slave
  * @param (void(GL_Actor<TYPENAME>::*func)( const TYPENAME )) rotate
  * @param (const TYPENAME) radian
  */
 template <typename TYPENAME> 
 inline void rotateMasterSlave( GL_Actor<TYPENAME>& master, GL_Actor<TYPENAME>& slave, 
                                void(GL_Actor<TYPENAME>::*rotate)( const TYPENAME ), const TYPENAME radian )
 {
    OrientationMatrix3<TYPENAME> original_orientation( master.getOrientation() );
    (master.*rotate)( radian );
    slave.setOrientation( master.getOrientation() * transpose( original_orientation ) * slave.getOrientation() );
 }
 
 #endif // GL_ACTOR_H